package cn.whale.kill.service.impl;

import cn.whale.basic.utils.DateTimeUtil;
import cn.whale.kill.domain.KillCourse;
import cn.whale.kill.domain.KillSessions;
import cn.whale.kill.mapper.KillSessionsMapper;
import cn.whale.kill.service.IKillCourseService;
import cn.whale.kill.service.IKillSessionsService;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import static cn.whale.basic.constants.RedisConstants.*;


/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author whale.chen
 * @since 2020-11-30
 */
@Service
@Slf4j
public class KillSessionsServiceImpl extends ServiceImpl<KillSessionsMapper, KillSessions> implements IKillSessionsService {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private IKillCourseService killCourseService;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void publishLess3DayKillSessions() {
        //性能考虑，使用hash的批量添加功能
        //处理秒杀场次=========================================================================================================
        //1.按照开始时间查询，上线最近3天的秒杀场次
        List<KillSessions> killSessions = publishKillSessions();

        //处理秒杀商品=========================================================================================================
        publishKillCourse(killSessions);

    }

    @Override
    public List<KillSessions> selectKillSessions() {
        //绑定场次的hash操作
        BoundHashOperations<String, String, KillSessions> killSessionsHashOperations = redisTemplate.boundHashOps(KEY_KILL_SESSIONS);
        return killSessionsHashOperations.values();
    }

    private void publishKillCourse(List<KillSessions> killSessions) {
        //绑定场次的hash操作
        BoundHashOperations<String, Object, Object> killCourseHashOperations = redisTemplate.boundHashOps(KEY_KILL_COURSE);
        //所有的秒杀场次的ID
        List<Long> sessionsIds = killSessions.stream().map(sessions -> sessions.getId()).collect(Collectors.toList());
        //4.查询场次下的商品，设置相关属性
        List<KillCourse> killCourses = killCourseService.selectBySessionsIds(sessionsIds);

        //秒杀商品的map
        Map<String, KillCourse> killCourseMap = new HashMap<>();

        //秒杀商品的key
        Set<Object> killCoursesKyes = killCourseHashOperations.keys();

        //增加秒杀场次和秒杀商品的关系  key_kill_relation:1 = [1_1,1_2,1_3]
        Map<String,List<String>> relation = new HashMap<>();

        killCourses.forEach( killCourse -> {

            //一个商品可能会出现在多个秒杀场次中，所以以：  场次ID_商品ID  作为key
            String killCoursesKye = killCourse.getSessionId()+"_"+killCourse.getId().toString();
            //幂等处理
            if(killCoursesKyes == null || !killCoursesKyes.contains(killCoursesKye)){

                //冗余时间和状态
                KillSessions currentSession = killSessions.stream().filter(sessions -> sessions.getId().equals(killCourse.getSessionId())).collect(Collectors.toList()).get(0);
                killCourse.setStartTime(currentSession.getStartTime());
                killCourse.setEndTime(currentSession.getEndTime());
                killCourse.setStatus(KillSessions.STATUS_ONLINE);

                //随机码，在执行秒杀的时候需要判断随机码
                String killCode = UUID.randomUUID().toString().replaceAll("-","");
                killCourse.setKillCode(killCode);

                //为每个商品生成一个信号量：库存，使用redisson实现
                RSemaphore semaphore = redissonClient.getSemaphore(String.format(KEY_KILL_COURSE_SEMAPHORE, killCoursesKye));
                semaphore.trySetPermits(killCourse.getKillCount());
                log.info("秒杀商品 {} 加入redis",killCourse.getCourseName());
                killCourseMap.put(killCoursesKye,killCourse);
                //增加秒杀场次和秒杀商品的关系  key_kill_relation:1 = [1_1,1_2,1_3]
                String keyRelation = String.format(KEY_KILL_COURSE_RELATION, killCourse.getSessionId());
                log.info("秒杀场次和商品关系 {} - {}",keyRelation,killCoursesKye);
                List<String> ids = relation.containsKey(keyRelation)?relation.get(keyRelation):new ArrayList<String>();
                ids.add(killCoursesKye);
                relation.put(keyRelation,ids);
            }

        });

        killCourseHashOperations.putAll(killCourseMap);

        //5.为了方便根据场次找到商品，专门来维护关系
        //增加秒杀场次和秒杀商品的关系  key_kill_relation:1 = [1_1,1_2,1_3]
        relation.keySet().forEach(sessionId->{
            List<String> killCourseIds = relation.get(sessionId);
            redisTemplate.boundListOps(sessionId).leftPushAll(killCourseIds);
        });
    }

    private List<KillSessions>  publishKillSessions() {
        Wrapper<KillSessions> wrapper = new EntityWrapper<KillSessions>().between("start_time", DateTimeUtil.startTime(), DateTimeUtil.endTime(2));
        List<KillSessions> killSessions = baseMapper.selectList(wrapper);

        //绑定场次的hash操作
        BoundHashOperations<String, String, KillSessions> killSessionsHashOperations = redisTemplate.boundHashOps(KEY_KILL_SESSIONS);

        //已经发布了的秒杀场次的key , 记录所有的秒杀场次的ID
        Set<String> sessionsKeys = killSessionsHashOperations.keys();

        //2.把list结构的场次列表转换成map结果，批量保存redis
        Map<String, KillSessions> killSessionsMap = new HashMap<>(killSessions.size());
        killSessions.forEach(sessions -> {
            //把场次设置到Redis , 做一下幂等处理，如果已经发布的就不发布了
            if(sessionsKeys == null || !sessionsKeys.contains(sessions.getId().toString())){
                log.info("秒杀场 {} 加入redis",sessions.getName());
                sessions.setStatus(KillSessions.STATUS_ONLINE);
                killSessionsMap.put(sessions.getId().toString(),sessions);
            }
        });

        //3.保持秒杀场次
        killSessionsHashOperations.putAll(killSessionsMap);
        return killSessions;
    }


}
